package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ApArticleStaticHtmlService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.CacheConstants;
import com.heima.common.exception.CustomException;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.article.vos.LoadBehaviorInfoVO;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.heima.common.constants.ArticleConstants.DEFAULT_TAG;

/**
 * @author brianxia
 * @version 1.0
 * @date 2022-10-19 10:49
 *
  */
@Service
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle>
        implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ApArticleStaticHtmlService apArticleStaticHtmlService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<ApArticle> load(ArticleHomeDto dto, Short status) {

        //1.size进行范围判断
        if(dto.getSize() <= 0 || dto.getSize() >= 1000){
            dto.setSize(10);
        }

        //2.对tag
        if(StringUtils.isBlank(dto.getTag())){
            dto.setTag(DEFAULT_TAG);
        }

        Page<ApArticle> page = new Page<>();
        page.setCurrent(1); //limit 10 = limit 0,10
        page.setSize(dto.getSize());
        page.setSearchCount(false); //不需要操作select count统计总数据量

        return apArticleMapper.selectArticleList(page,dto,status);
    }

    @Override
    public List<ApArticle> load2(ArticleHomeDto dto, Short status, boolean cache) {

        if(cache){
            //走缓存
            String content = stringRedisTemplate.opsForValue().get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE
                    + dto.getTag());
            if(StringUtils.isNotBlank(content)){
                return JSON.parseArray(content, ApArticle.class);
            }
        }

        //走数据库
        return load(dto,status);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveArticle(ArticleDto dto) {

        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto,apArticle);
        //1.根据id是否存在判断是新增还是修改
        if(dto.getId() == null){
            //2.新增添加三张表数据 配置表用默认值
            save(apArticle);

            //配置表
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
            apArticleConfigMapper.insert(apArticleConfig);

            //内容表
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());

            apArticleContentMapper.insert(apArticleContent);

        }else{
            //3.修改 只需要修改两张表 主表+内容表
            updateById(apArticle);

            apArticleContentMapper.update(null,
                    Wrappers.<ApArticleContent>lambdaUpdate()
                            .eq(ApArticleContent::getArticleId,apArticle.getId())
                            .set(ApArticleContent::getContent,dto.getContent())
                    );
        }

        //创建静态页面
        try {
            apArticleStaticHtmlService.createStaticHtml(apArticle,dto.getContent());
        } catch (Exception e) {
           log.error("生成静态页面失败",e);
           throw new CustomException(AppHttpCodeEnum.SERVER_ERROR,"生成静态页面失败");
        }

        //4.返回主键id
        return apArticle.getId();
    }

    @Override
    public LoadBehaviorInfoVO loadArticleBehavior(ArticleInfoDto dto) {

        ApUser user = AppThreadLocalUtil.getUser();
        if(user == null){
            throw new CustomException(AppHttpCodeEnum.NEED_LOGIN);
        }

        //获取状态
        //1.点赞
        LoadBehaviorInfoVO loadBehaviorInfoVO = new LoadBehaviorInfoVO();

        String userId = user.getId().toString();
        String key = CacheConstants.LIKES + dto.getArticleId();
        Boolean member = stringRedisTemplate.opsForSet().isMember(key, userId);
        if(member != null && member){
            loadBehaviorInfoVO.setIslike(true);
        }

        //2.关注
        key = CacheConstants.FOLLOW_FANS + dto.getAuthorId();
        member = stringRedisTemplate.opsForSet().isMember(key, userId);
        if(member != null && member){
            loadBehaviorInfoVO.setIsfollow(true);
        }

        return loadBehaviorInfoVO;
    }

    @Override
    public void updateArticleBehavior(ArticleVisitStreamMess mess) {

        //1.更新数据库中的值
        apArticleMapper.updateBehaviorCount(mess.getLike(),mess.getComment(),
                mess.getView(),mess.getCollect(),mess.getArticleId());


        //2.计算文章分值
        ApArticle apArticle = apArticleMapper.selectById(mess.getArticleId());
        int score = computeArticleScore(apArticle);
        //无法做到今日点赞以及相关操作分值 * 3，修改数据库将点赞数拆分成今日之前和今日，
        // 凌晨将今日数量搬运到今日之前
        //score = score * 3;

        //3.更新缓存中的分值
        replaceArticleDataInCache(apArticle,score,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        replaceArticleDataInCache(apArticle,score,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + DEFAULT_TAG);

    }

    private void replaceArticleDataInCache(ApArticle apArticle, int score, String key) {

        //1.获取redis中的数据
        String s = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isBlank(s)){
            return;
        }

        //2.判断缓存中是否包含这篇文章 是更新分值，否加入到文章中
        boolean flag = false;
        List<HotArticleVo> articleVos = JSONArray.parseArray(s, HotArticleVo.class);
        for (HotArticleVo articleVo : articleVos) {
            if(Objects.equals(articleVo.getId(),apArticle.getId())){
                BeanUtils.copyProperties(apArticle,articleVo);
                articleVo.setScore(score);
                flag = true;
                break;
            }
        }

        if(!flag){
            //没找到 加入到集合中
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(apArticle,hotArticleVo);
            hotArticleVo.setScore(score);
            articleVos.add(hotArticleVo);
        }
        //3.排序 + 取前30条
        List<HotArticleVo> collect = articleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(30).collect(Collectors.toList());

        //4.更新到redis
        String s1 = JSON.toJSONString(collect);
        if(!s.equals(s1)){
            stringRedisTemplate.opsForValue().set(key,s1);
        }

    }

    private int computeArticleScore(ApArticle apArticle) {
        int score = 0;

        //阅读
        Integer views = apArticle.getViews();
        if(views != null){
            score += views * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        //点赞
        Integer likes = apArticle.getLikes();
        if(likes != null){
            score += likes * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        //收藏
        Integer collection = apArticle.getCollection();
        if(collection != null){
            score += collection * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        //评论
        Integer comment = apArticle.getComment();
        if(comment != null){
            score += comment * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }

        return score;
    }
}
